﻿/**
 * Универсальный локальный RPC мост, AS2
 *
 * @version	3.3
 * @author	meps
 */
class meps.net.CLocalBridge {
	
	private static var STATE_NONE:Number = 0;
	private static var STATE_SERVER:Number = 1; // ожидание сервером соединения с клиентами
	private static var STATE_CLIENT:Number = 2; // ожидание клиентом команды инициализации после его регистрации на сервере
	private static var STATE_CONNECT:Number = 3; // состояние полноценного двустороннего соединения
	private static var STATE_CLOSE:Number = 4;
	
	private static var METHOD_CONNECT:String = "__connect";
	private static var METHOD_CALL:String = "__call";
	private static var METHOD_CLOSE:String = "__close";
	private static var METHOD_RETAIN:String = "__retain";
	
	private static var HEX:String = "0123456789abcdef";
	
	///////////////////////////////////////////////////////////////////////////
	// INSTANCE
	
	private var _state:Number; // текущее состояние
	private var _target:Object; // целевой объект
	private var _client:Object; // клиентский объект для вызова методов
	private var _name:String; // имя соединения на передачу данных
	private var _buffer:Array /*Array*/; // буфер исходящих запросов
	private var _lc:LocalConnection;
	
	private var _onConnect:Function;
	private var _onCall:Function;
	private var _onClose:Function;
	
	/**
	 * Конструктор
	 *
	 * @param   обработчик удаленных вызовов;
	 */
	public function CLocalBridge(client:Object, target:Object) {
		//trace("2_CLocalBridge.constructor()");
		_state = STATE_NONE;
		_target = target ? target : this;
		_client = client ? client : target;
		_buffer = [ ];
		
		_lc = new LocalConnection();
		_lc[METHOD_CONNECT] = delegate(this, doConnect);
		_lc[METHOD_CALL] = delegate(this, doCall);
		_lc[METHOD_CLOSE] = delegate(this, doClose);
		_lc.allowDomain = function(domain:String):Boolean {
			return true;
		}
	}
	
	/**
	 * Установить двустороннее соединение
	 *
	 * <p>После установления соединения по переданному базовому
	 * идентификатору оно автоматически переносится на генерируемый
	 * уникальный идентификатор, освобождая базовый для последующих
	 * вызовов.</p>
	 *
	 * <p>Если было установлено соединение, то при вызове оно сбрасывается.
	 * При вызове метода буфер удаленных вызовов очищается. Все удаленные
	 * вызовы, запрошенные до момента фактического установления
	 * двустороннего соединения помещаются в буфер, а после установления
	 * соединения вызываются в том же порядке.</p>
	 *
	 * @param   базовый идентификатор соединения;
	 *
	 * @return  ...
	 */
	public function connect(name:String):Boolean {
		//trace("2_CLocalBridge.connect(" + name + ")");
		if (name)
			name = name.split(":").join("");
		if (!name || (_state != STATE_NONE && _state != STATE_CLOSE))
			return false;
		
		_state = STATE_SERVER;
		_buffer.length = 0; // буфер принудительно очищается при каждом новом соединении
		
		// попытка стать сервером
		if (!_lc.connect(name)) {
			// сервер уже существует
			_state = STATE_CLIENT;
			var n:String = uniqueID();
			//trace("2_CLocalBridge.client id:" + n);
			_lc.connect(n);
			_lc.send(name, METHOD_CONNECT, n);
		}
		return true;
	}
	
	/**
	 * Вызов удаленного метода
	 *
	 * <p>Метод самостоятельно решает проблему асинхронности запуска
	 * источника и получателя вызовов. Если двустороннее соединение еще не
	 * установлено, то все запросы попадают в исходящий буфер и будут
	 * отправлены в том же порядке сразу после установления соединения.</p>
	 *
	 * @param	method	имя метода;
	 *
	 * @param	args	параметры метода;
	 *
	 * @return  ...
	 */
	public function call(method:String):Boolean {
		//trace("2_CLocalBridge.call(" + arguments + ")");
		var args:Array = arguments.slice();
		if (_state == STATE_CONNECT) {
			args.unshift(_name, METHOD_CALL);
			//trace("2_CLocalBridge.send args:" + args);
			_lc.send.apply(_lc, args);
		} else if (_state == STATE_SERVER || _state == STATE_CLIENT) {
			//trace("2_CLocalBridge.store args:" + args);
			_buffer.push(args);
		}
		
		return true;
	}
	
	/**
	 * Разорвать соединение
	 */
	public function close():Boolean {
		//trace("2_CLocalBridge.close()");
		if (_state == STATE_CLOSE)
			return false;
		
		if (_state == STATE_CONNECT) {
			// разорвать двустороннее соединение на втором получателе
			_lc.send(_name, METHOD_CLOSE);
			_lc.close();
		} else if (_state == STATE_SERVER || _state == STATE_CLIENT) {
			_lc.close();
		}
		
		_state = STATE_CLOSE;
		return true;
	}
	
	/**
	 * Событие установления двустороннего соединения.
	 */
	public function get onConnect():Function {
		return _onConnect;
	}
	
	/**
	 * @private
	 */
	public function set onConnect(value:Function):Void {
		_onConnect = value;
	}
	
	/**
	 * Событие получения удаленного вызова.
	 */
	public function get onCall():Function {
		return _onCall;
	}
	
	/**
	 * @private
	 */
	public function set onCall(value:Function):Void {
		_onCall = value;
	}
	
	/**
	 * Событие завершения соединения удаленным вызовом.
	 */
	public function get onClose():Function {
		return _onClose;
	}
	
	/**
	 * @private
	 */
	public function set onClose(value:Function):Void {
		_onClose = value;
	}
	
	public function toString():String {
		return "[CLocalBridge state:" + _state +
			", name:" + _name +
			"]";
	}
	
	///////////////////////////////////////////////////////////////////////////
	// PRIVATE
	
	private function doConnect(name:String):Void {
		//trace("2_CLocalBridge.onConnect id:" + name);
		if (_state == CLocalBridge.STATE_SERVER) {
			// клиент соединился с сервером
			var n:String = uniqueID();
			_lc.close();
			_lc.connect(n);
			_lc.send(name, CLocalBridge.METHOD_CONNECT, n);
		}
		
		_name = name;
		_state = CLocalBridge.STATE_CONNECT;
		if (_target == undefined)
			_onConnect.call(_client, this);
		else
			_onConnect.call(_target, this);
		
		// отправить очередь исходящих команд
		while (_buffer.length > 0) {
			var a:Array = Array(_buffer.shift());
			a.unshift(_name, CLocalBridge.METHOD_CALL);
			//trace("2_CLocalBridge.resend args:" + a);
			_lc.send.apply(_lc, a);
		}
	}
	
	private function doCall():Void {
		//trace("2_CLocalBridge.onCall(" + arguments + ")");
		var args:Array = arguments.slice();
		var m:String = String(args.shift());
		var fn:Function = _client[m];
		if (fn == undefined)
			return;
		
		if (_target == undefined) {
			fn.apply(_client, args);
			_onCall.call(_client, this);
		} else {
			fn.apply(_target, args);
			_onCall.call(_target, this);
		}
	}
	
	private function doClose():Void {
		//trace("2_CLocalBridge.onClose()");
		if (_state == CLocalBridge.STATE_CLOSE)
			return;
		
		if (_state == CLocalBridge.STATE_SERVER ||
			_state == CLocalBridge.STATE_CLIENT ||
			_state == CLocalBridge.STATE_CONNECT)
			_lc.close();
		
		_state = CLocalBridge.STATE_CLOSE;
		if (_target == undefined)
			onClose.call(_client, this);
		else
			onClose.call(_target, this);
	}
	
	// потому, что mx.utils.Delegate фактически не работает
	private function delegate(target:Object, func:Function):Function {
		var f = function() {
			var tr:Object = arguments.callee.tr;
			var fn:Function = arguments.callee.fn;
			return fn.apply(tr, arguments);
		}
		
		f.tr = target;
		f.fn = func;
		return f;
	}
	
	// потому, что стандартный Number().toString() лажает для больших чисел
	private function hex(num:Number):String {
		var h:String = "";
		while (num != 0) {
			h = HEX.charAt(num & 0xF) + h;
			num >>= 4;
		}
		if (h.length == 0)
			return "0";
		
		return h;
	}
	
	private function uniqueID():String {
		return "_" + hex((new Date()).valueOf()) + hex(Math.random() * 2147483647);
	}
	
}
